//
// Created by jaesonzhang on 2024/9/12.
//

#include "JSEngine.h"

#include <pthread.h>

JSEngine::JSEngine() {}


/* also used to initialize the worker context */
static JSContext *JS_NewCustomContext(JSRuntime *rt) {
  JSContext *ctx;
  ctx = JS_NewContext(rt);
  if (!ctx)
    return NULL;
  /* system modules */
  js_init_module_std(ctx, "std");
  js_init_module_os(ctx, "os");
  return ctx;
}

int has_suffix(const char *str, const char *suffix) {
  size_t len = strlen(str);
  size_t slen = strlen(suffix);
  return (len >= slen && !memcmp(str + len - slen, suffix, slen));
}

void JSEngine::create() {
  _thread = std::thread(&JSEngine::processTasks, this);

  std::packaged_task<void()> task([this] {
    jsRuntime = JS_NewRuntime();
    js_std_set_worker_new_context_func(JS_NewCustomContext);
    js_std_init_handlers(jsRuntime);
    jsContext = JS_NewCustomContext(jsRuntime);

    /* loader for ES6 modules */
    JS_SetModuleLoaderFunc(jsRuntime, NULL, js_module_loader, NULL);

    JS_SetHostPromiseRejectionTracker(jsRuntime,
                                      js_std_promise_rejection_tracker,
                                      NULL);

    js_std_add_helpers(jsContext, 0, NULL);

    /* make 'std' and 'os' visible to non module code */
    const char *str = "import * as std from 'std';\n"
                      "import * as os from 'os';\n"
                      "globalThis.std = std;\n"
                      "globalThis.os = os;\n";
    evalBuf(jsContext, str, strlen(str), "<input>", JS_EVAL_TYPE_MODULE);
  });

  enqueueTask(task);
}


void JSEngine::destroy() {
  {
    std::lock_guard<std::mutex> lock(task_mutex_);
    stop_thread_ = true;
  }
  task_cv_.notify_one();
  if (_thread.joinable()) {
    _thread.join();
  }

  if (dumpMemory) {
    JSMemoryUsage stats;
    JS_ComputeMemoryUsage(jsRuntime, &stats);
    JS_DumpMemoryUsage(stdout, &stats, jsRuntime);
  }

  js_std_free_handlers(jsRuntime);
  JS_FreeContext(jsContext);
  JS_FreeRuntime(jsRuntime);
}

void JSEngine::run(const std::string &filename) {
  auto weak = weak_from_this();
  std::packaged_task<void()> task([weak, filename] {
    auto strong = weak.lock();
    if (!strong) {
      return;
    }
    int ret = evalFile(strong->jsContext,
                       std::move(filename.c_str()),
                       JS_EVAL_TYPE_GLOBAL);
  });
  enqueueTask(task);
}

void JSEngine::run(const char *code, int len, const std::string &filename) {
  auto weak = weak_from_this();
  std::packaged_task<void()> task([weak, code, len, filename, this] {
    auto strong = weak.lock();
    if (!strong) {
      return;
    }
    int ret =
        evalBuf(strong->jsContext,
                code,
                len,
                filename.c_str(),
                JS_EVAL_TYPE_GLOBAL);
  });
  enqueueTask(task);
}

int JSEngine::evalBuf(JSContext *ctx, const char *buf, int buf_len,
                      const char *filename, int eval_flags) {
  JSValue val;
  int ret;

  if ((eval_flags & JS_EVAL_TYPE_MASK) == JS_EVAL_TYPE_MODULE) {
    /* for the modules, we compile then run to be able to set
       import.meta */
    val = JS_Eval(ctx, buf, buf_len, filename,
                  eval_flags | JS_EVAL_FLAG_COMPILE_ONLY);
    if (!JS_IsException(val)) {
      js_module_set_import_meta(ctx, val, TRUE, TRUE);
      val = JS_EvalFunction(ctx, val);
    }
    val = js_std_await(ctx, val);
  } else {
    val = JS_Eval(ctx, buf, buf_len, filename, eval_flags);
  }
  if (JS_IsException(val)) {
    js_std_dump_error(ctx);
    ret = -1;
  } else {
    ret = 0;
  }
  JS_FreeValue(ctx, val);
  return ret;
}

int JSEngine::evalFile(JSContext *ctx, const char *filename, int module) {
  uint8_t *buf;
  int ret, eval_flags;
  size_t buf_len;

  buf = js_load_file(ctx, &buf_len, filename);
  if (!buf) {
    perror(filename);
    exit(1);
  }

  if (module < 0) {
    module = (has_suffix(filename, ".mjs") ||
        JS_DetectModule((const char *) buf, buf_len));
  }
  if (module)
    eval_flags = JS_EVAL_TYPE_MODULE;
  else
    eval_flags = JS_EVAL_TYPE_GLOBAL;
  ret = evalBuf(ctx,
                reinterpret_cast<const char *>(buf),
                buf_len,
                filename,
                eval_flags);
  js_free(ctx, buf);
  return ret;
}

// 将任务添加到队列中
template<typename T>
void JSEngine::enqueueTask(T &&task) {
  {
    std::lock_guard<std::mutex> lock(task_mutex_);
    tasks_.emplace(std::move(task)); // 使用 std::move 来移动任务
  }
  task_cv_.notify_one();
}

// JSApp 主循环，main loop which calls the user JS callbacks
void JSEngine::processTasks() {
  extern int (*os_poll_func)(JSContext *ctx);
  int err;
  bool hasTask = false;
  // 确保线程已创建
  if (_thread.native_handle()) {
    std::cerr << "Thread handle is invalid" << std::endl;
  }

  // 设置线程名称，检查是否成功
  int result = pthread_setname_np(_thread.native_handle(), "JS_Thread");
  if (result != 0) {
    std::cerr << "Thread set name failed" << std::endl;
  }

  while (true) {
    std::packaged_task<void()> task;
    {
      std::unique_lock<std::mutex> lock(task_mutex_);
//      task_cv_.wait(lock,
//                    [this] {
//                      return stop_thread_ || !tasks_.empty()
//                          || JS_IsJobPending(jsRuntime) == TRUE;
//                    });
      if (stop_thread_) {
        break;
      }

      if (!tasks_.empty()) {
        task = std::move(tasks_.front());
        tasks_.pop();
        hasTask = true;
      }
    }

    if (hasTask) {
      task(); // 执行任务
      hasTask = false;
    }

    if (JS_IsJobPending(jsRuntime)) {
      printf("has job pending");
      // 执行循环
      err = JS_ExecutePendingJob(jsRuntime, &jsContext);
      if (err <= 0) {
        if (err < 0) {
          js_std_dump_error(jsContext);
        }
      }
    }

    // 执行 os poll 中的事件
    if (os_poll_func != nullptr) {
      os_poll_func(jsContext);
    }
    std::this_thread::sleep_for(std::chrono::microseconds(10));
  }
}

